<?php

/**
 * Access plugin
 * Access logined user
 * Take acl map from xml file
 *
 * @category   Core
 * @package    Core_Controller
 * @subpackage Plugin
 *
 * @version  $Id: Acl.php 87 2010-08-29 10:15:50Z vadim.leontiev $
 */
class Core_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
{

    /**
     * Plugin configuration settings array
     *
     * @var array
     */
    protected $_options = array();

    /**
     * Denied page settings
     *
     * @var array
     */
    protected $_deniedPage = array(
        'module'     => 'default',
        'controller' => 'error',
        'action'     => 'denied'
    );

    /**
     * Url to redirect when user unlogined
     *
     * @var string
     */
    protected $_unLogined = '/';

    /**
     * default role name
     *
     * @var string
     */
    protected $_roleName = 'guest';

    /**
     * ACL object
     *
     * @var Zend_Acl
     */
    protected $_acl;

    /**
     * Zend_Cache
     *
     * @var object
     */
    protected $_cache = null;
    
    /**
     *
     * @param array $options 
     */
    public function  __construct(Array $options = array())
    {
        if (isset($options['denied'])) {
            $this->_deniedPage = array(
                'module'     => $options['denied']['module'],
                'controller' => $options['denied']['controller'],
                'action'     => $options['denied']['action']
            );
        }

        if (isset($options['role'])) {
            $this->_roleName = $options['role'];
        }

        if (isset($options['unlogined'])) {
        	$this->_unLogined = $options['unlogined'];
        }

        $this->_options = $options;
    }

    /**
     * Sets the ACL object
     *
     * @param  Zend_Acl $acl
     * @return void
     **/
    public function setAcl(Zend_Acl $acl)
    {
        $this->_acl = $acl;
    }

    /**
     * Returns the ACL object
     *
     * @return Zend_Acl
     **/
    public function getAcl()
    {
        $this->loadCache();

        if (null == $this->_acl) {
            $config = $this->_getConfig();
            $this->setAcl(new Core_Acl($config));
            $this->saveCache();
        }
        
        Zend_Registry::set('Zend_Acl', $this->_acl);

        return $this->_acl;
    }

    /**
     * Load Zend_Acl from cache
     *
     * @return void
     */
    public function loadCache()
    {
        if (($cache = $this->getCache()) !== false && $this->getCache()->test('Zend_Acl')) {
            $this->_acl = $cache->load('Zend_Acl');
        }
        return;
    }

    /**
     * Save Zend_Acl to cache
     *
     * @return void
     */
    public function saveCache()
    {
        if(($cache = $this->getCache()) !== false) {
            $cache->save($this->_acl, 'Zend_Acl');
        }
        return;
    }

    /**
     * Get cache from ACL
     *
     * @return Zend_Cache_Core|false
     */
    public function getCache()
    {
        if (Zend_Registry::get('Zend_Cache_Manager')->hasCache('acl')) {
            $cache = Zend_Registry::get('Zend_Cache_Manager')->getCache('acl');
            return $cache;
        }
        return false;
    }

    /**
     * Sets the ACL role to use
     *
     * @param string $roleName
     * @return void
     */
    public function setRoleName($roleName)
    {
        $this->_roleName = $roleName;
    }

    /**
     * Returns the ACL role used
     *
     * @return string
     */
    public function getRoleName()
    {
        $identity = Zend_Auth::getInstance()->getIdentity();

        if ($identity) {
            $this->_roleName = $identity->role;
        }

        return $this->_roleName;
    }

    /**
     * Verify resource access from users
     * Add ACL to Zend_Registry
     *
     * @param  Zend_Controller_Request_Abstract $request
     * @return bool
     * @throws Login_Exception Error resource
     */
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        $acl = $this->getAcl();
        $resource = $request->getModuleName() . '/' . $request->getControllerName();
        $action   = $request->getActionName();

        /** Check resource */
        if (!$this->getAcl()->has($resource) && Zend_Controller_Front::getInstance()->getParam('env') == 'development') {
            $this->getResponse()->appendBody('<h2>Resource "'.$resource.'" not found in ACL rules</h2>');
            $this->_allowAccess();
            return;
        }

        if ($acl->isAllowed($this->getRoleName(), $resource, $action) === false) {
            $this->_denyAccess();
            return;
        } else {
            $this->_allowAccess();
            return;
        }
    }

    /**
     * Deny Access Function
     * Redirects to errorPage,
     * this can be called from an action using the action helper
     *
     * @return void
     */
    protected function _denyAccess()
    {
        if (Zend_Auth::getInstance()->hasIdentity() || $this->_request->getActionName() === 'logout') {
	        $this->_request->setModuleName($this->_deniedPage['module'])
                           ->setControllerName($this->_deniedPage['controller'])
                           ->setActionName($this->_deniedPage['action'])
                           ->setDispatched(false);
        } else {
            $session = new Zend_Session_Namespace('Zend_Request');
            $session->params = $this->_request->getParams();

            $this->_response->setRedirect($this->_unLogined)->sendResponse();
        }
    }

    /**
     * Allow Access function
     * 
     */
    protected function _allowAccess()
    {
        $session = new Zend_Session_Namespace('Zend_Request');

        if (!empty($session->params) && Zend_Auth::getInstance()->hasIdentity()) {
            $this->_request->setModuleName($session->params['module'])
                           ->setControllerName($session->params['controller'])
                           ->setActionName($session->params['action'])
                           ->setParams($session->params)
                           ->setDispatched(false);
            $session->params = null;
        }
    }

    /**
     * Create config rules array
     * @return array
     */
    protected function _getConfig()
    {
        $config = array(
            'roles' => $this->_getAllRoles(),
            'resources' => $this->_getAllResources(),
            'rules' => $this->_getAllRules()
        );

        return $config;
    }

    /**
     * Get all roles
     *
     * @return array
     */
    protected function _getAllRoles()
    {
        $query = Zend_Db_Table::getDefaultAdapter()->select()
            ->from(array('u'=>'roles'),
                 array('role'))
            ->joinLeft(array('uj'=>'roles'),
                 'uj.role_id = u.parent_id',
                 array('uj.role AS parent'))
            ->order(array('u.parent_id','u.role_id'));

        $stmt = $query->query();
        $rows = array();

        foreach ($stmt->fetchAll() as $row) {
            $rows[$row['role']] = $row['parent'];
        }

        return $rows;
    }

    /**
     * Get all resources
     *
     * @return array
     */
    protected function _getAllResources()
    {
        $query = Zend_Db_Table::getDefaultAdapter()->select()
            ->from(array('r'=>'resources'),
                 array('resource'))
            ->joinLeft(array('rj'=>'resources'),
                 'rj.resource_id = r.parent_id',
                 array('rj.resource AS parent'))
            ->order(array('r.parent_id','r.resource_id'));

        $stmt = $query->query();
        $rows = array();

        foreach ($stmt->fetchAll() as $row) {
            $rows[$row['resource']] = $row['parent'];
        }

        return $rows;
    }

    /**
     * Get all rules
     *
     * @return array
     */
    protected function _getAllRules()
    {
        $query = Zend_Db_Table::getDefaultAdapter()->select()
            ->from(array('r'=>'rules'),
                    array('allow'))
            ->joinLeft(array('rs'=>'resources'),
                    'rs.resource_id = r.resource_id',
                    array('rs.resource'))
            ->joinLeft(array('a'=>'actions'),
                    'a.action_id = r.action_id',
                    array('a.action'))
            ->joinLeft(array('rl'=>'roles'),
                    'rl.role_id = r.role_id',
                    array('rl.role'))
            ->order(array('r.role_id','r.resource_id','r.action_id','r.allow'));

        $stmt = $query->query();
        $rows = array();

        foreach ($stmt->fetchAll() as $row) {
            $rows[] = array(
                'rule' => ($row['allow'] == 1 ? 'allow' : 'deny'),
                'role' => $row['role'],
                'resource' => $row['resource'],
                'action' => $row['action']
            );
        }

        return $rows;
    }
}